import {
    IHeapSnapshot, 
    IHeapNode, 
    IHeapEdge,
    Nullable, 
    TraceFinder,
    LeakTracePathItem 
} from "@memlab/core";

/**
 * 查找器
 */
export class Finder {
    private static traceFinder: TraceFinder = new TraceFinder();

    /**
     * 判断是否是指定名称属性的边
     * @param edge 边
     * @param propName 名称
     * @returns true/false
     */
    public static isPropEdgeWithName(edge: IHeapEdge, propName: string) : boolean {
        return edge.type === 'property' && edge.name_or_index === propName;
    }

    /**
     * 判断是否是Proto属性的边
     * @param edge 边
     * @returns true/false
     */ 
    public static isProtoPropEdge(edge: IHeapEdge) {
        return edge.type === 'property' && edge.name_or_index === 'Proto';
    }

    /**
     * 判断是否是hclass属性的边
     * @param edge 边
     * @returns true/false
     */ 
    public static isHClassPropEdge(edge: IHeapEdge) {
        return edge.type === 'property' && edge.name_or_index === 'hclass';
    }

    /**
     * 共享内存类型
     * 例举关键类型
     */
    public static OOM_SHARED_TYPES: string[] = [
        'js_shared_object',
        'js_shared_array',
        'js_shared_map',
        'js_shared_set'
    ];

    public static STRING_UNIQUE_ID = 'shared_heap_string_unique_id_0123456789';

    public static isSharedHeapNode(node: IHeapNode) : boolean {
        // String 类对象属于 shared heap
        if (node.isString) {
            return true
        }
        // 共享内存类型与节点名称匹配的
        if (Finder.OOM_SHARED_TYPES.includes(node.name)) {
            return true
        }
        // 先判断 Proto
        let protoEdge = node.findAnyReference((edge: IHeapEdge) => { return Finder.isProtoPropEdge(edge) })
        if (protoEdge) {
            // 通过 proto 查找是否是 shared heap
            if (this.OOM_SHARED_TYPES.includes(protoEdge.toNode.name)) {
                return true
            }
            return this.isSharedHeapNode(protoEdge.toNode)
        }
        // 再判断 hclass
        let hclassEdge = node.findAnyReference((edge: IHeapEdge) => { return this.isHClassPropEdge(edge) })
        if (hclassEdge === null) {
            return false
        }
        return this.isSharedHeapNode(hclassEdge.toNode)
    }

    public static getSharedHeapNodesMap(snapshot: IHeapSnapshot) : Map<string, Array<IHeapNode>> { 
        let nodesMap = new Map<string, Array<IHeapNode>>();
        snapshot.nodes.forEach((node: IHeapNode, ) => {
            if (Finder.isSharedHeapNode(node)) {
                let tag = node.isString ? Finder.STRING_UNIQUE_ID : node.name;
                let nodes = nodesMap.get(tag);
                if (nodes === undefined) {
                    nodes = new Array<IHeapNode>();
                } 
                nodes.push(node);
                nodesMap.set(tag, nodes);
            }
        });
        return nodesMap;
    }

    /**
     * 获取所有shared heap节点的retained size大小总和
     * @param snapshot heapsnapshot
     */
    public static getSharedHeapNodeRetainedSize(snapshot: IHeapSnapshot) : number { 
        let retained_size = 0;
        snapshot.nodes.forEach((node: IHeapNode, ) => {
            if (Finder.isSharedHeapNode(node)) {
                retained_size += node.retainedSize;
            }
        });
        return retained_size;
    }

    /**
     * 获取所有string类型对象的retained size大小总和
     * @param snapshot heapsnapshot
     * @returns retained size of shared heap string
     */
    public static getStringRetainedSize(snapshot: IHeapSnapshot) : number {
        let retained_size = 0;
        snapshot.nodes.forEach((node: IHeapNode, ) => {
            if (node.isString) {
                retained_size += node.retainedSize;
            }
        });
        return retained_size;
    }
    
    public static isSharedHeapNodeId(snapshot: IHeapSnapshot, nodeId: number) : boolean {
        const node = snapshot.getNodeById(nodeId)
        if (node === null) {
            return false
        }   
        return Finder.isSharedHeapNode(node)
    }

    /**
     * 是否是GCRoot节点
     * @param node 目标节点
     * @returns true/false
     */
    public static isGCRoot(targetNode: IHeapNode): boolean {
        return targetNode.numOfReferrers === 0;
    }

    /**
     * 是否是Distance为1的节点
     * @param targetNode 目标节点
     * @returns true/false
     */
    public static isEqualDistanceOne(targetNode: IHeapNode): boolean {
        let result = false;
        targetNode.pathEdge
        targetNode.referrers.forEach((referrer: IHeapEdge) => {
            if (referrer.fromNode.numOfReferrers === 0) {
                result = true;
            }
        });
        return result;
    }

    /**
     * 查找指定ID的节点
     * @param snapshot 快照对象 IHeapSnapshot
     * @param nodeId 节点ID
     * @returns 
     */
    public static findNodeById(snapshot: IHeapSnapshot, nodeId: number) : Nullable<IHeapNode> {
        return snapshot.getNodeById(nodeId);
    }

    /**
     * 查找满足过滤条件的节点
     * @param snapshot 快照对象 IHeapSnapshot
     * @param filter 过滤器
     * @returns IHeapNode Array
     */
    public static findNodesByFilter(snapshot: IHeapSnapshot, filter: (node: IHeapNode) => boolean) : Array<IHeapNode> {
        const result = new Array<IHeapNode>();
        snapshot.nodes.forEach((node: IHeapNode, ) => {
            if (filter(node)) {
                result.push(node);
            }
        });
        return result;
    }

    /**
     * 按过滤器和过滤条件查找节点
     * @param snapshot 快照对象 IHeapSnapshot
     * @param filter 过滤器
     * @param condition 过滤条件
     * @returns IHeapNode Array
     */
    public static findNodesByFilterCondition(snapshot: IHeapSnapshot, 
        filter: (node: IHeapNode, condition: any) => boolean, condition: any) : Array<IHeapNode> {
        const result = new Array<IHeapNode>();
        snapshot.nodes.forEach((node: IHeapNode, ) => {
            if (filter(node, condition)) {
                result.push(node);
            }
        });
        return result;
    }

    /**
     * 查找所有GCRoot节点
     * @param snapshot 快照对象 IHeapSnapshot
     * @returns IHeapNode Array
     */
    public static findGCRoots(snapshot: IHeapSnapshot) : Array<IHeapNode> {
        let filter = (node: IHeapNode) => Finder.isGCRoot(node);
        return Finder.findNodesByFilter(snapshot, filter);
    }

    /**
     * 查找Detached节点
     * @param snapshot 快照对象 IHeapSnapshot
     * @returns IHeapNode Array
     */
    public static findDetachedNodes(snapshot: IHeapSnapshot) : Array<IHeapNode> {
        let filter = (node: IHeapNode) => node.is_detached;
        return Finder.findNodesByFilter(snapshot, filter);
    }

    /**
     * 查找指定个数的Detached节点
     * @param snapshot 快照对象 IHeapSnapshot
     * @param nodeName 节点名称
     * @returns IHeapNode Array
     */
    public static findDetachedNodesByName(snapshot: IHeapSnapshot, nodeName: string) : Array<IHeapNode> {
        const result = new Array<IHeapNode>();
        snapshot.nodes.forEach((node: IHeapNode, ) => {
            if (node.is_detached && node.name === nodeName) {
                result.push(node);
            }
        });
        return result;
    }

    /**
     * find nodes by distance from gc root
     * @param snapshot snapshot
     * @param distance distance <= 1
     * @returns Array<IHeapNode>
     */
    public static findNodesByDistanceOne(snapshot: IHeapSnapshot, includeGCRoot: boolean = false) : Array<IHeapNode> {
        let filter = (node: IHeapNode) => {
            return (includeGCRoot && Finder.isGCRoot(node)) || Finder.isEqualDistanceOne(node);
        };
        return Finder.findNodesByFilter(snapshot, filter);
    }

    /**
     * 查找指定节点名称的节点
     * @param snapshot 快照对象 IHeapSnapshot
     * @param nodeName 节点名称
     * @returns IHeapNode Array
     */
    public static findNodesByName(snapshot: IHeapSnapshot, nodeName: string) : Array<IHeapNode> {
        let filter = (node: IHeapNode) => {
            return node.name == nodeName;
        };
         return Finder.findNodesByFilter(snapshot, filter);
    }

    /**
     * 查找指定节点名称的节点，最大个数为
     * @param snapshot 快照对象 IHeapSnapshot
     * @param nodeName 节点名称
     * @param count 查找对象个数（按节点id号排序）
     * @param reverse 是否倒序查找
     * @returns IHeapNode Array
     */
     public static findNodesByNameCount(
        snapshot: IHeapSnapshot, 
        nodeName: string, 
        count: number) : Array<IHeapNode> { 
        const result = new Array<IHeapNode>();
        let nodeCount = 0;
        snapshot.nodes.forEach((node, )=> {
            if((node.name == nodeName) && (nodeCount < count)) {
                result.push(node);
                nodeCount += 1;
            }
        });
        return result; 
    }

    /**
     * 
     * @param snapshot 快照对象 IHeapSnapshot
     * @param nodeName 节点名称
     * @returns 
     */
    public static findNodesByNameDistanceOne(snapshot: IHeapSnapshot, nodeName: string, includeGCRoot: boolean = false) : Array<IHeapNode> {
        let filter = (node: IHeapNode) => {
            return (node.name === nodeName) 
                && ((includeGCRoot && Finder.isGCRoot(node)) || Finder.isEqualDistanceOne(node));
        };
        return Finder.findNodesByFilter(snapshot, filter);
    }

    /**
     * 查找指定节点的最短引用路径
     * @param snapshot 快照对象 IHeapSnapshot
     * @param targetNode 节点对象
     * @returns 引用链路径
     */
    public static findShortestPathToGCRoot(snapshot: IHeapSnapshot, targetNode: IHeapNode): LeakTracePathItem | undefined {
        Finder.traceFinder.annotateShortestPaths(snapshot);  // 标记最短路径
        const optPath = Finder.traceFinder.getPathToGCRoots(snapshot, targetNode);
        return optPath ? optPath : undefined;
    }

    /**
     * 查找指定节点数组的所有最短引用路径
     * @param snapshot 快照对象 IHeapSnapshot
     * @param targetNodes 节点对象数组
     * @returns 引用链路径数组
     */
    public static findShortestPaths(snapshot: IHeapSnapshot, targetNodes: IHeapNode[]): Array<LeakTracePathItem> {
        const result: Array<LeakTracePathItem> = new Array<LeakTracePathItem>();
        Finder.traceFinder.annotateShortestPaths(snapshot);
        targetNodes.forEach(node => {
            const optPath = Finder.traceFinder.getPathToGCRoots(snapshot, node);
            if (optPath) {
                result.push(optPath);
            }
        });
        return result;
    }
}